const dgram= require('dgram');
const _= require('lodash');
const Blockchain= require('./Blockchain');

class Communication extends Blockchain{
    constructor(){
        super();
        this.seed= {port: 8001, address: '192.168.1.215'};
        this.peers= [];
        this.udp= dgram.createSocket('udp4');
        this.udp.on('listening', ()=>{
            const address= this.udp.address();
            console.log('UDP正在监听端口', address.port);
        });
        this.udp.on('message', (data, remote)=>{
            const action= JSON.parse(data)
            if(action.type){
                this.dispatch(action, remote);
            }
        });
        const port= Number(process.argv[2])|| 0;
        this.udp.bind(port);
        if(port!== this.seed.port){
            this.say({type: 'newpeer'}, this.seed)
            this.peers.push(this.seed)
        }
        process.on('exit', ()=>{
            console.log('已终止UDP')
        })
    }
    dispatch(action, remote){
        console.log('收到消息', action.type)
        switch(action.type){
            case 'newpeer':
                console.log('欢迎新朋友！', remote.port)

                // 1、告知 IP
                this.say({
                    type: 'remoteAddress',
                    data: remote,
                }, remote);

                // 2、告知 peerList
                this.say({
                    type: 'peerList',
                    data: this.peers,
                }, remote);

                // 3、广播全场，通知所有节点添加新节点
                this.broadcast({
                    type: 'sayHello',
                    data: remote,
                });

                // 4、同步区块链
                this.say({
                    type: 'blockchain',
                    data: {
                        blockchain: this.blockchain,
                        blockdata: this.data,
                    }
                }, remote);

                if(!this.peerExists(remote))
                    this.peers.push(remote);

                break;
            case 'blockchain':
                this.replaceChain(action.data.blockchain);
                break;
            case 'mine':
                this.addMine(action.data, remote.port);
                break;
            case  'remoteAddress':
                this.remote= action.data;
                console.log('我的地址信息为', this.remote);
                break;
            case 'peerList':
                _.each(action.data, p=>{
                    if(!this.peerExists(p))
                        this.peers.push(p);
                });
                console.log('节点列表', this.peers)
                break;
            case 'sayHello':
                // if(_.isEqual(this.remote, action.data))
                //     break;  //不用问候自己
                console.log(`问候新朋友${action.data.port}`)
                this.say({
                    type: 'hello',
                    data: '相识就是缘，请你喝茶'
                }, action.data)
                if(!this.peerExists(action.data))
                    this.peers.push(action.data);
                break;
            case 'hello':
                console.log(`${remote.port}：${action.data}`)
                break;
            default:
                console.log('信息无效')
        }
    }
    /*
      使用 isValidChain() 校验新的区块链，实质是循环调用 isValidBlock() 校验了所有的区块
      1、校验区块的工作量证明、哈希值等等；
      2、校验区块的每笔交易记录是否合法（应当循环调用 isValidTransfer() 校验每笔记录）；
      3、校验区块的矿工奖励是否合法；
     */
    replaceChain(newchain){
        if(newchain.length> this.blockchain.length && this.isValidChain(newchain)){
            this.blockchain= newchain;
            // this.blockchain= JSON.parse(JSON.stringify(newchain));  //无需深拷贝
        }else
            console.log('收到无效的区块链，已放弃处理')
    }
    peerExists(remote){
        return _.find(this.peers, p=> p.port=== remote.port && p.address=== remote.address);
    }
    say(something, target){
        something= JSON.stringify(something);
        this.udp.send(something, target.port, target.address);
    }
    broadcast(something){
        _.each(this.peers, peer=> this.say(something, peer));
    }
    mine(){
        const block= super.mine();
        if(block){
            this.broadcast({
                type: 'mine',
                data: block,
            });
        }
        return block;
    }
    addMine(newBlock, port){
        const lastBlock= this.getLastBlock();
        if(newBlock.hash=== lastBlock.hash)
            return;
        if(this.isValidBlock(newBlock, lastBlock)){
            this.blockchain.push(newBlock);
            this.broadcast({
                type: 'mine',
                data: newBlock,
            });
            console.log(port, '挖矿成功！');
        }
    }
}

module.exports=Communication;